package scheduler.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import scheduler.core.task.TaskPoolBase;
import scheduler.core.utils.Utils;
import scheduler.core.utils.ValidationException;
import scheduler.core.utils.Point;

/**
 *
 * @author Volodymyr Pogorelov
 */
public abstract class Processor {
    protected Map<Integer, TaskPoolBase> poolMap = new HashMap<>();
    protected List<TaskPoolBase> poolList = new ArrayList<>();
 
 /*
  * Main method of the class.
  */
    public void calc(int prior) throws ValidationException {
       validatePrior(prior);
       calcTi_and_Tpi(prior);
       calcN();
    }
    
    /*
     * Використовується для побудови графіка
     */
    public List<Point> getTiPoints(){
        int n = poolList.size();
        List<Point> res = new ArrayList<>(n);
        try{
            for(Integer i : poolMap.keySet()){
                calc(i);
                res.add(new Point(i, Ti));
            }
        }catch(ValidationException e){ 
            System.err.println(e);
        } // Виключна ситуація не може виникнути (дані правильні)
        return res;
    }
    
    public List<Point> getTpiPoints(){
        int n = poolList.size();
        List<Point> res = new ArrayList<>(n);
        try{
            for(Integer i : poolMap.keySet()){
                calc(i);
                res.add(new Point(i, Tpi));
            }
        }catch(ValidationException e){ 
            System.err.println(e);
        } // Виключна ситуація не може виникнути (дані правильні)
        return res;
    }
    
    public List<Point> getTpijPoints(){
        int n = poolList.size();
        List<Point> res = new ArrayList<>(n);
        try{
            for(Integer i : poolMap.keySet()){
                calc(i);
                res.add(new Point(i, Tpij));
            }
        }catch(ValidationException e){ 
            System.err.println(e);
        } // Виключна ситуація не може виникнути (дані правильні)
        return res;
    }
    
/************* Наступні поля доступні для перегляду в графічному інтерфейсі 
                              (ВИХІДНІ ДАНІ).                               ***/
    
//=====================  Перша група вихідних даних ========================//
    /**
     * Середній час виконання задач і-го пріоритету
    */
    private double Ti; // Τ(i)
    /**
     * Середній час виконання задачі з урахуванням очікування в чергах для
     * і-го пріоритету
     */
    private double Tpi;
    /**
     * Абсолютна пропускна спроможність
     */
    private double Ai; // А(і)
    
    /**
     * Загальна кількість задач у системі 
     */
    private int N; // N
    
    /**
     * Середній час виконання j-ї задачі з урахуванням очікування в черзі для
     * і-го пріоритету
     */
    private double Tpij;
    
//=====================  Друга група вихідних даних ========================//
    /**
     * Гранична стаціонарна ймовірність відсутності задач і-го пріорітету в черзі
     */
    private double P0_2; // P0(i)
    /**
     * Приведена інтенсивність моделі
     */
    private double Ro_2; // ρ
    /**
     * Середня інтенсивність виконання задач і-го пріорітету
     */
    private double lambdai; // λ(i)
    /**
     * Середній час виконання задач і-го пріорітету в автономному режимі
     */
    private double nui; // ν(i)
    /**
     * Середня інтенсивність потоку задач на обслуговування у моделі
     */
    private double lambda_2; // λ
    /**
     * Середній час обслуговування задач у моделі
     */
    private double nu_2; // ν
    /**
     * Загальна кількість задач алгоритму (може не співпадати із загальної кі-тю задач в системі)
     */
    private int n_2; // n
    /**
     * Загальна кількість задач і-го пріорітету
     */
    private int ni; // n(i)
    
//=====================  третя група вихідних даних ========================//
    /**
     * Гранична стаціонарна ймовірність відсутності задач від 1 до і-1 рівнів 
     * пріорітетів разом
     */
    private double P0_1; // P0(1,2,...,i-1)
    /**
     * Приведена інтенсивність моделі
     */
    private double Ro_1; // ρ
    /**
     * Середня інтенсивність потоку задач на обслуговування у моделі
     */
    private double lambda_1; // λ
    /**
     * Середній час обслуговування задач у моделі
     */
    private double nu_1; // ν
    /**
     * Загальна кількість задач алгоритму (може не співпадати із загальної кі-тю задач в системі)
     */
    private int n_1; // n

    public double getTi() {
        return Ti;
    }

    public double getP0_2() {
        return P0_2;
    }

    public double getRo_2() {
        return Ro_2;
    }

    public double getLambdai() {
        return lambdai;
    }

    public double getNui() {
        return nui;
    }

    public double getLambda_2() {
        return lambda_2;
    }

    public double getNu_2() {
        return nu_2;
    }

    public int getN_2() {
        return n_2;
    }

    public int getNi() {
        return ni;
    }

    public double getP0_1() {
        return P0_1;
    }

    public double getRo_1() {
        return Ro_1;
    }

    public double getLambda_1() {
        return lambda_1;
    }

    public double getNu_1() {
        return nu_1;
    }

    public int getN_1() {
        return n_1;
    }

    public int getN() {
        return N;
    }
    
    public double getTpi() {
        return Tpi;
    }
    
    public double getAi() {
        return Ai;
    }
    
    public double getTpij() {
        return Tpij;
    }
/*****************************************************************************/
    
    private void validatePrior(int i)throws ValidationException{ 
         if(!poolMap.containsKey(i)){
            throw new ValidationException(ValidationException.ErrorType.INVALID_PRIORITY, 
                    "Невірно заданий пріоритет. Пріоритету " + i + " не існує.");
         }
    }
    
    private void calcLambda_1(int i){
        double res = 0;
        int N = 0;
        int  j = 0;
        for(TaskPoolBase pool : poolList){
            if(j >= i - 1){
                break;
            }
            res += pool.getN() * pool.getLambdai();
            N += pool.getN();
            j++;
        }
        n_1 = N;
        if(n_1 == 0){
            lambda_1 = 0;
            return;
        }
        res /= N;
        lambda_1 = res;
    }
    
    private void calcNu_1(int i) {
        double res = 0;
        int N = 0;
        int j = 0;
        for(TaskPoolBase pool : poolList){
            if(j >= i - 1){
                break;
            }
            res += pool.getN() * pool.getNui();
            N += pool.getN();
            j++;
        }
        if(N == 0){
            nu_1 = 0;
            return;
        }
        res /= N;
        nu_1 = res;
    }
    
    private void calcRo_1(int i) {
        calcLambda_1(i);
        calcNu_1(i);
        Ro_1 = lambda_1 * nu_1;
    }
    
    private void calcP0_1(int i) { // P0 1 .. i-1
        calcRo_1(i);
        P0_1 = Utils.calcP0(n_1, Ro_1);
    }
    
    private void calcP0_2(int i) { // P0 i
        TaskPoolBase pool = poolMap.get(i);
        lambda_2 = lambdai = pool.getLambdai();
        calcP0_1(i);
        nui = pool.getNui(); 
        nu_2 = nui / P0_1;
        Ro_2 = lambda_2 * nu_2;
        n_2 = ni = pool.getN();
        P0_2 = Utils.calcP0(n_2, Ro_2);
    }
    
    private void calcN(){
        N = 0;
        for(TaskPoolBase pool : poolList){
            N += pool.getN();
        }
    }
    
    private void calcAi(){
        Ai = (1 - P0_2) / nu_2;
    }
    
    private void calcTi_and_Tpi(int i) throws ValidationException { // T(i) and Tp(i)
        calcP0_2(i);
        calcAi();
        Ti = nu_2*(n_2 - (1-P0_2) / Ro_2);
        Tpi = Ti + nu_2 * P0_2;
        Tpij = Tpi + 1;
    }
}
